﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Xml;
using FinanSoft.Utilities;
using FinanSoft.Entities;
using FinanSoft.BusinessLayer;
using System.Collections;

namespace FinanSoft.DataLayer
{
    public class AccountingEntryDataManager
    {
        #region Fields
        /// <summary>
        /// Atributo privado del tipo de la clase. Unica instancia de esta clase que existira
        /// </summary>
        private static AccountingEntryDataManager _instance;
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor privado de la clase para permitir la implementacion del patron singleton
        /// </summary>
        private AccountingEntryDataManager()
        {

        }
        #endregion

        #region Methods
        /// <summary>
        /// Metodo que obtienen la instancia existente de la clase. Si esta aun no ha sido instanciada, la instancia.
        /// </summary>
        public static AccountingEntryDataManager GetInstance()
        {
            if (_instance == null)
                _instance = new AccountingEntryDataManager();
            return _instance;
        }

        /// <summary>
        /// Metodo que registra un asiento en la base de datos
        /// </summary>
        /// <param name="newEntry"> Nuevo asiento con todos los atributos que requiere el registro</param>
        /// <returns></returns>
        public bool AddAccountingEntry(AccountingEntry newEntry)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_ADD_ACCOUNTING_ENTRY;

                SqlParameter[] parameters = {new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_SEASON_ID, newEntry.Season.Id),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_DOCUMENT_DATE, newEntry.DocDate),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_ACCOUNTING_DATE, newEntry.AccountingDate),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_DOCUMENT_TYPE_ID, newEntry.Type.Id),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_USER_ID, newEntry.User.Id),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_REFERENCE1, newEntry.Ref1),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_REFERENCE2, newEntry.Ref2),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_REFERENCE3, newEntry.Ref3),
                                             new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNTING_ENTRY_ACCOUNTS, newEntry.DetailsToXML().InnerXml)};


                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Utilities.Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Actualiza el saldo de una cuenta auxiliar incrementando el monto dado. El incremento puede ser al debe o al haber lo cuál suma o resta dependiendo
        /// de si el saldo acutal de la cuenta está al debe o al haber.
        /// </summary>
        /// <param name="auxiliarAccountId">Id de la cuenta que se desea actualizar</param>
        /// <param name="balanceIncrement">Monto que se está incrementando al saldo de la cuenta</param>
        /// <param name="incrementType">Tipo de incremento (si es al debe o al haber), que provoca que el saldo incremente o disminuya dependiendo de su saldo</param>
        /// <returns>Verificación de si la operación fue exitosa</returns>
        public bool UpdateAuxiliarAccountBalance(int auxiliarAccountId, double balanceIncrement, Constants.AccountingEntryType incrementType)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_UPDATE_AUXILIAR_ACCOUNT_BALANCE;

                SqlParameter[] parameters = {new SqlParameter(Constants.SP_PARAM_UPDATE_AUXILIAR_ACCOUNT_BALANCE_AUX_ACC_ID, auxiliarAccountId),
                                             new SqlParameter(Constants.SP_PARAM_UPDATE_AUXILIAR_ACCOUNT_BALANCE_INCREMENT, balanceIncrement),
                                             new SqlParameter(Constants.SP_PARAM_UPDATE_AUXILIAR_ACCOUNT_BALANCE_AL_DEBE, incrementType == Constants.AccountingEntryType.debit)};

                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Utilities.Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Obtiene el XML con la lista de detalles para un Asiento particular y la convierte en una lista de objetos detalle, la cual retorna.
        /// </summary>
        /// <param name="IdAccountingEntry"> El entero, identificador unico del asiento para el que se consultaran los detalles</param>
        /// <returns> La lista de objetos detalle</returns>
        private List<AccountingEntryDetail> GetAccountingEntryDetails(int IdAccountingEntry)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_GET_ACCOUNTING_ENTRY_DETAILS;

                SqlParameter[] parameters = {new SqlParameter(Constants.SP_PARAM_GET_ACCOUNTING_ENTRY_DETAILS_ID, IdAccountingEntry)};


                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Utilities.Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);

                if (result.HasRows)
                {
                    StringBuilder sb = new StringBuilder("");
                    while(result.Read())
                        sb.Append(result.GetString(0));
                    dataHelper.CloseReader();
                    // Se crea un documento XML con el string recibido
                    XmlDocument xmlAccountingEntryDetails = new XmlDocument();
                    xmlAccountingEntryDetails.LoadXml(sb.ToString());
                    return BuildAccountingEntryDetails(xmlAccountingEntryDetails);
                }
                else
                    return null;
            }
            catch (Exception ex)
            {
                return null;
            }
             
        }

        /// <summary>
        /// Metodo que recibe el xml a parsear para convertirlo en una lista de detalles de un asiento
        /// </summary>
        /// <param name="xmlDetails">El xml que contiene los campos de los objetos detalle a crear e insetar en la lista</param>
        /// <returns> La lista de objetos detalle</returns>
        private List<AccountingEntryDetail> BuildAccountingEntryDetails(XmlDocument xmlDetails)
        {            
            List<AccountingEntryDetail> detailsList;
            XmlNodeList nodeChildren;
            int id;
            int accId;
            int auxAccId;
            Currency currency;
            double ammount;
            bool found;
            Constants.AccountingEntryType type;
            AccountingEntryDetail currentDetail;

            detailsList = new List<AccountingEntryDetail>();
            nodeChildren = xmlDetails.GetElementsByTagName(Constants.NODE_NAME_DETAIL);
            foreach (XmlNode node in nodeChildren)
            {                
                currentDetail = new AccountingEntryDetail();
                found = false;

                //Carga la id en el objeto Detalle
                id = Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_DETAIL_ID].InnerText);
                currentDetail.Id = id;

                //Carga la cuenta en el objeto Detalle
                accId = Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_ACCOUNT_ID].InnerText);
                currentDetail.Account = CatalogManager.GetInstance().GetAccount(accId);

                //Carga el tipo de movimiento al objeto detalle. Se recibe 1 si es debito, 0 si es credito
                type = System.Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_TYPE].InnerText) == 1 ? Constants.AccountingEntryType.debit : Constants.AccountingEntryType.credit;
                currentDetail.Type = type;

                //Carga el Id de la cuenta Auxiliar
                auxAccId = Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_AUX_ACCOUNT_ID].InnerText);
                currentDetail.AuxiliarAccountId = auxAccId;

                currency = MoneyManager.GetInstance().GetCurrency(Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_CURRENCY].InnerText));
                                
                //Carga el monton del movimiento dependiendo del tipo de moneda
                if (currency.Id == MoneyManager.GetInstance().LocalCurrency.Id)
                {
                    ammount = System.Convert.ToDouble(node[Constants.TAG_NAME_AMMOUNT].InnerText.Replace('.', ','));
                    currentDetail.LocAmmount = ammount;

                    currentDetail.SysAmmount = 0;
                }
                else
                {
                    ammount = System.Convert.ToDouble(node[Constants.TAG_NAME_AMMOUNT].InnerText.Replace('.', ','));
                    currentDetail.SysAmmount = ammount;

                    currentDetail.LocAmmount = 0; 
                }
                foreach (AccountingEntryDetail detail in detailsList)
                {
                    if (detail.Id == currentDetail.Id)
                    {
                        detail.LocAmmount = detail.LocAmmount == 0? currentDetail.LocAmmount : detail.LocAmmount;
                        detail.SysAmmount = detail.SysAmmount == 0? currentDetail.SysAmmount : detail.SysAmmount;
                        found = true;
                        break;
                    }
                }

                if((detailsList.Count == 0) || !found)
                    detailsList.Add(currentDetail);                
            }

            return detailsList;
        }

        /// <summary>
        /// Metodo que obtiene un conjunto de asientos de la base de datos de acuerdo al filtro proporcionado y los retorna como un lista
        /// </summary>
        /// <param name="xmlFilter"> XML con los parametros que funcionaran como filtro para la busqueda de asientos</param>
        /// <returns></returns>
        public List<AccountingEntry> GetAccountingEntries(string xmlFilter)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_GET_ACCOUNTING_ENTRIES;

                SqlParameter[] parameters = {new SqlParameter(Constants.SP_PARAM_GET_ACCOUNTING_ENTRIES_FILTER, xmlFilter)};


                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Utilities.Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                
                if (result.HasRows)
                {
                    StringBuilder sb = new StringBuilder("");
                    while(result.Read())
                        sb.Append(result.GetString(0));
                    dataHelper.CloseReader();
                    // Se crea un documento XML con el string recibido
                    XmlDocument xmlAccountingEntryDetails = new XmlDocument();
                    xmlAccountingEntryDetails.LoadXml(sb.ToString());
                    return BuildAccountingEntries(xmlAccountingEntryDetails);
                }
                else
                return new List<AccountingEntry>();
            }  
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// Metodo que recibe el xml a parsear para convertirlo en una hashtable de asientos
        /// </summary>
        /// <param name="xmlDetails">El xml que contiene los campos de los objetos asiento a crear e insertar en la lista</param>
        /// <returns> La lista de objetos asiento</returns>
        private List<AccountingEntry> BuildAccountingEntries(XmlDocument xmlAsientos)
        {
            List<AccountingEntry> entriesTable;
            XmlNodeList nodeChildren;
            int entryId;
            int seasonId;
            int docType;
            String reference;
            DateTime docDate;
            DateTime accDate;
            AccountingEntry currentEntry;

            entriesTable = new List<AccountingEntry>();
            nodeChildren = xmlAsientos.GetElementsByTagName(Constants.NODE_NAME_ACCOUNTING_ENTRY);
            
            foreach (XmlNode node in nodeChildren)
            {
                //Formato del XML: <Principal><Asiento IdAsiento="1" IdTipo="AS" FechaDoc="24/10/2010" FechaCont="24/10/2010" ref1="" ref2="" ref3="" IdTemporada="1"/> <Asiento .../> ... </Principal>
                currentEntry = new AccountingEntry();

                //Carga la id del objeto asiento
                entryId = Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_ID].InnerText);
                currentEntry.Id = entryId;
                               
                //Carga el tipo de documento al objeto asiento. Se recibe un string de largo 2 con el id del tipo y este se obtiene del bussiness layer
                docType = System.Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_TYPE_ID].InnerText);
                currentEntry.Type = DocumentTypeManager.GetInstance().GetType(docType);

                //Carga la fecha de documento del asiento
                docDate = System.Convert.ToDateTime(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_DOC_DATE].InnerText);
                currentEntry.DocDate = docDate;
                
                //Carga la fecha de contabilizacion del asiento
                accDate = System.Convert.ToDateTime(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_ACC_DATE].InnerText);
                currentEntry.AccountingDate = accDate;
                
                //Carga el primer campo de referencia al objeto asiento. Se recibe un string
                reference = System.Convert.ToString(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_REF1].InnerText);
                currentEntry.Ref1 = reference;

                //Carga el segundo campo de referencia al objeto asiento. Se recibe un string
                reference = System.Convert.ToString(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_REF2].InnerText);
                currentEntry.Ref2 = reference;

                //Carga el tercer campo de referencia al objeto asiento. Se recibe un string
                reference = System.Convert.ToString(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_REF3].InnerText);
                currentEntry.Ref3 = reference;
                
                //Carga la temporada en la que se registro el asiento. Se recibe un entero con el id de la temporada y esta se obtiene del bussiness layer
                seasonId = System.Convert.ToInt32(node[Constants.TAG_NAME_ACCOUNTING_ENTRY_SEASON_ID].InnerText);
                currentEntry.Season = PeriodManager.GetInstance().GetSeason(seasonId);

                //Carga la lista de detalles del asiento
                currentEntry.EntryDetails = GetAccountingEntryDetails(entryId);

                entriesTable.Add(currentEntry);
            }

            return entriesTable;
        } 
        
        #endregion
    }
}
